package hydrap2p.library.hashers.common;

import hydrap2p.helper.Utility;
import hydrap2p.library.HashData;
import hydrap2p.library.HashGenerator;
import hydrap2p.library.Range;
import hydrap2p.library.SharedItemInfo;
import hydrap2p.logging.Level;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.LinkedList;
import java.util.List;

import org.apache.thrift.TException;

/**
 * Shows to the Library the implementation of one hash algorithm
 * @author Hufman
 *
 */
public class HashServer implements HashGenerator.Iface {
	private final String LOGSOURCE;
	private static final int READSIZE=1024*512;
	private final String algorithm;
	private MessageDigest hasher;
	
	public HashServer(String algorithm, String messageDigestName) throws NoSuchAlgorithmException
	{
		LOGSOURCE = "Common Hash Algorithms - "+algorithm;
		this.algorithm=algorithm;
		hasher=MessageDigest.getInstance(messageDigestName);
		hasher.reset();
	}
	
	/**
	 * http://stackoverflow.com/questions/332079/in-java-how-do-i-convert-a-byte-array-to-a-string-of-hex-digits-while-keeping-le/1059731#1059731
	 * @param bytes
	 * @return
	 */
	private static String toHexString(byte[] bytes) {
	    char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
	    char[] hexChars = new char[bytes.length*2];
	    int c = 0;
	    for ( int j = 0; j < bytes.length; j++ ) {
	        int v = bytes[j] & 0xFF;
	        hexChars[c] = hexArray[v/16];
	        c++;
	        hexChars[c] = hexArray[v%16];
	        c++;
	    }
	    return new String(hexChars, 0, c); }

	
	@Override
	public String getHash(SharedItemInfo item, Range range) {
		int totalread=0;
		int read=1;
		byte[] temp=new byte[READSIZE];
		
		try
		{
			HashLogging.log(Level.VERBOSE, LOGSOURCE, "Hashing file "+Utility.appendPath(item.pathname, item.name)+" range "+range.start+"-"+(range.start+range.len));
			File file=new File(Utility.appendPath(item.pathname, item.name));
			RandomAccessFile random=new RandomAccessFile(file,"r");
			random.seek(range.start);
			while (totalread<range.len && read>0)
			{
				read=random.read(temp);
				if (read>0)
				{
					hasher.update(temp, 0, read);
					totalread+=read;
				}
			}
			random.close();
			
			// finish the output
			temp=hasher.digest();
			String output=toHexString(temp);
			HashLogging.log(Level.VERBOSE, LOGSOURCE, "Hashed file "+Utility.appendPath(item.pathname, item.name)+" to "+output);
			return output;
		} catch (IOException e)
		{
			HashLogging.log(Level.MISHAP, LOGSOURCE, "Problem reading file "+Utility.appendPath(item.pathname, item.name)+" for hashing: "+e.getLocalizedMessage());
		}
		return null;
	}

	@Override
	public List<HashData> getHashes(SharedItemInfo item) {
		File file=new File(Utility.appendPath(item.pathname, item.name));
		long size=file.length();
		
		HashData rethash=new HashData();
		rethash.setHashdata(getHash(item, new Range(0,size)));
		rethash.setAlgorithm(algorithm);
		rethash.setRange(new Range(0,size));
		rethash.setUid(item.uid);

		if (rethash.getHashdata()!=null)
		{
			List<HashData> ret=new LinkedList<HashData>();
			ret.add(rethash);
			return ret;
		}
		else
		{
			return null;
		}
	}

	@Override
	public void ping() throws TException {
	}

	@Override
	public List<String> supportedInterfaces() throws TException {
		List<String> interfaces=new LinkedList<String>();
		interfaces.add("Service");
		interfaces.add("HashGenerator");
		return interfaces;
	}

}
